class Map<K,V>{
    set(key: K, value:V) {
        for (let i = this.keys.length; i >=0; i--) {
            if(this.keys[i]==key){
                this.values[i] =value;
                return
            }
        }
        let length=this.keys.length;
        this.keys.concat(key);
        this.values.concat(value);
    }
    forEach(func: (value: any, key: any, map: any) => void) {
        for (let i = this.keys.length-1; i >=0 ; i--) {
            func(this.keys[i],this.values[i],this);
        }
    }
    keys:K[]
    values:V[]
    has(key: K): boolean {
        for (var k in this.keys){
            if (k==key){
                return true;
            }
        }
        return false;
    }
    get(key: K): V{
        for (let i = this.keys.length-1; i >=0; i--) {
            if(this.keys[i]==key){
                return this.values[i];
            }
        }
    }
}

class HasName{
    name:string;
}
class HasChildren<C extends HasName> extends HasName{
    children: Map<String,C>=new Map();
}
class Column extends HasName{
    description:String;
    dataType:String;
}
class Table extends HasChildren<Column>{
    description: string;
}
class Database extends HasChildren<Table>{
}
let NEW_LINE="\r\n";
let SPACE="\t";
interface ErElement<T,P>{
    disable():void;
    accept(t:T,p:P):void;
    toString():string;
}
interface IField extends ErElement<Column,Entity>{
}
enum Key{
    PK,FK,UK
}
class Field implements IField{
    enabled:boolean;
    name:String;
    dataType:String;
    keys:Key[];
    description:String;
    parent:Entity;
    disable(){
        this.enabled=false;
    }
    accept(column:Column,parent:Entity){
        this.enabled=true;
        this.name=column.name;
        this.parent=parent
        this.description=column.description;
        if(this.dataType===undefined){
            this.dataType=column.dataType
        }
    }
    toString():string {
        var out=SPACE+SPACE+this.dataType;
        out+=' '+this.name+' ';
        this.keys.forEach(k=>{
            out+=k.toString();
        })
        out+=" \""+this.description+" "+NEW_LINE
        return out;
    }
}
abstract class AbstractErElement<T extends HasChildren<any>,P> extends HasChildren<any> implements ErElement<T,P>{
    enabled: boolean;
    children:Map<string,ErElement<any,any>> =new Map();
    parent:P
    accept(t:T,p:P){
        this.enabled=true;
        this.parent=p;
        t.children.forEach((value,key,map)=>{
            var child=this.children.has(value.name)?this.children.get(value.name):this.newChild();
            child.accept(value,this);
            this.children.set(value.name,child);
        })
    }
    protected abstract newChild():ErElement<any,any>;

    disable() {
        this.enabled=false;
        this.children.forEach(c=>c.disable());
    }
}
class Entity extends AbstractErElement<Table,ErDocument>{
    description: string;

    accept(t: Table,d:ErDocument) {
        super.accept(t,d);
        this.description=t.description;
    }

    protected newChild(): ErElement<any,Entity> {
        return new Field();
    }

    toString(): string {
        var out = SPACE + this.name;
        if(this.description!==undefined){
            out+='['+this.description+']';
        }
        out+='{'+NEW_LINE
        this.children.forEach(c=>{
            out+=c.toString();
        })
        out+=SPACE+'}'+NEW_LINE
        return out;
    }
}
class Relation{
    pk:Field;
    must:boolean;
    fk:Field;
    n:number;
    toString():string{
        var out=this.fk.parent.name;
        switch (this.n){
            case 0:
            case 1:
            case 2:
                out+="}|"
        }
        out+='--'
        out+=this.must?"||":"|o";
        out+=this.pk.parent.name;
        out+=':';
        out+=this.fk.name+NEW_LINE
        return out;
    }
}
class ErDocument extends AbstractErElement<Database,any>{
    relations:Relation[];
    protected newChild(): ErElement<any,ErDocument> {
        return new Entity();
    }
    toString() {
        if(!this.enabled){
            return "";
        }
        var out= 'erDiagram'+NEW_LINE;
        this.children.forEach(entiry =>{
            out+=entiry.toString();
            this.relations.forEach(r=>{
                if(r.fk.parent==entiry && r.pk.parent.name<=entiry.name){
                    out+=r.toString();
                }else if(r.pk.parent==entiry && r.fk.parent.name<entiry.name){
                    out+=r.toString();
                }
            })
        })
        return out;
    }
}